from dataclasses import dataclass, is_dataclass
from typing import Type, Any, Callable, Optional, Literal, Union, get_origin, get_args, TypeVar, NamedTuple
from .errors import JsonPatErrorWithPath, JsonVerificationError, JsonPatClassError
from .typing import is_named_tuple, is_optional_type


_T = TypeVar("_T")


def from_json(v: Any, cls: Type[_T] = Any) -> _T:
    return jsonpat_convert(cls, v, _obj_from_json)


def to_json(v: Any, cls: type = Any) -> Any:
    return jsonpat_convert(cls, v, _obj_to_json)


def ignore_unused_json_fields(cls: type, /):
    setattr(cls, "__ignore_unused_json_fields__", True)
    return cls


def _prepend_err_path(e: JsonPatErrorWithPath, component: Union[str, int]) -> JsonPatErrorWithPath:
    err = dict(e.__dict__)
    err["path"].insert(0, component)
    err_type: Any = e.__class__
    return err_type(**err)


def jsonpat_convert(cls: Any, v: Any, convert_callback: Callable[[Type[object], Any], Any]):
    if cls is Any:
        if type(v) in [int, str, float, bool, type(None)]:
            return v
        else:
            return convert_callback(cls, v)

    if cls is None:
        if v is not None:
            raise JsonVerificationError([], v, "不是null")
        return v

    if cls in [int, str, float, bool, type(None)]:
        if type(v) is not cls:
            if hasattr(v, '__repr__'):
                raise JsonVerificationError([], v, f"不是{cls.__name__}, 实际类型为{type(v).__name__}, 值为{v.__repr__()}")
            else:
                raise JsonVerificationError([], v, f"不是{cls.__name__}, 实际类型为{type(v).__name__}")
        return v

    origin_type = get_origin(cls)
    if origin_type is Literal:
        if v not in get_args(cls):
            raise JsonVerificationError([], v, "不是指定常量中的任何一个")
        return v

    if origin_type is Union:
        candidate_types = get_args(cls)
        for sub_type in candidate_types:
            try:
                return jsonpat_convert(sub_type, v, convert_callback)
            except JsonVerificationError:
                continue
        raise JsonVerificationError([], v, f"不是任何一种指定类型, 实际类型为{type(v).__name__}")

    if origin_type is tuple:
        if not isinstance(v, list):
            raise JsonVerificationError([], v, "不是Array")
        inner_types = get_args(cls)
        if len(inner_types) != len(v):
            raise JsonVerificationError([], v, f"数组长度不等于{len(inner_types)}")
        result = []
        for index, inner_type in enumerate(inner_types):
            try:
                value = jsonpat_convert(inner_type, v[index], convert_callback)
            except JsonPatErrorWithPath as e:
                raise _prepend_err_path(e, index)
            result.append(value)
        return tuple(result)

    if origin_type is list:
        if not isinstance(v, list):
            raise JsonVerificationError([], v, "不是Array")

        (inner_type,) = get_args(cls)
        result = []
        for index, element in enumerate(v):
            try:
                value = jsonpat_convert(inner_type, element, convert_callback)
            except JsonPatErrorWithPath as e:
                raise _prepend_err_path(e, index)
            result.append(value)
        return result

    if origin_type is dict:
        if not isinstance(v, dict):
            raise JsonVerificationError([], v, "不是Object")

        (key_type, value_type) = get_args(cls)
        result = {}
        for key, value in v.items():
            res_key = jsonpat_convert(key_type, key, convert_callback)
            try:
                res_value = jsonpat_convert(value_type, value, convert_callback)
            except JsonPatErrorWithPath as e:
                raise _prepend_err_path(e, str(key))
            result[res_key] = res_value
        return result

    if origin_type is not None:
        raise JsonPatClassError([], cls)

    return convert_callback(cls, v)


def _obj_from_json(cls: type, v, is_partial = False):
    if cls is Any:
        return v

    assert isinstance(cls, type)
    f = getattr(cls, "__from_json__", None)
    if f is not None:
        return f(v)

    if is_dataclass(cls) or is_named_tuple(cls) or cls is object:
        if not isinstance(v, dict):
            raise JsonVerificationError([], v, "不是Object")
        v: dict[str, Any]

        result = {}
        for base in cls.__bases__:
            if base in [object, tuple]:
                continue
            result_from_base = _obj_from_json(base, v, is_partial=True)
            if not hasattr(result_from_base, '__dict__'):
                raise JsonPatClassError([], base)
            result.update(result_from_base.__dict__)

        unused_keys = set(v.keys())
        for key, anno in getattr(cls, "__annotations__", {}).items():
            if key not in v:
                if is_optional_type(anno):
                    result[key] = None
                else:
                    raise JsonVerificationError([key], v, f"缺少属性: `{key}`")
            else:
                unused_keys.remove(key)
                try:
                    result[key] = from_json(v[key], anno)
                except JsonPatErrorWithPath as e:
                    raise _prepend_err_path(e, key)

        if not is_partial and not hasattr(cls, "__ignore_unused_json_fields__"):
            if len(unused_keys) > 0:
                raise JsonVerificationError([], v, "不应包含下列属性: " + ', '.join(f'`{s}`' for s in unused_keys))

        return cls(**result)

    raise JsonPatClassError([], cls)


def _obj_to_json(cls: type, v: Any):
    if cls is not Any:
        assert isinstance(cls, type)
        if not isinstance(v, cls):
            raise JsonVerificationError([], v, f"不能被转化为{cls.__name__}")

    f = getattr(v, "__to_json__", None)
    if f is not None:
        return f()

    value_cls = v.__class__
    if cls is Any:
        if value_cls in {list, tuple}:
            return [to_json(e) for e in v]
        if value_cls is dict:
            result = {}
            for key, value in v.items():
                try:
                    key_str = to_json(key)
                except:
                    raise JsonPatClassError([], value_cls)
                try:
                    result[key] = to_json(value)
                except JsonPatErrorWithPath as e:
                    raise _prepend_err_path(e, str(key_str))
            return result
        cls = value_cls

    if is_dataclass(cls) or is_named_tuple(cls) or cls is object:
        result = {}
        for base in cls.__bases__:
            if base in [object, tuple]:
                continue
            result_from_base = _obj_to_json(base, v)
            if not isinstance(result_from_base, dict):
                raise JsonPatClassError([], base)
            result.update(result_from_base)

        for key, anno in getattr(cls, "__annotations__", {}).items():
            value = getattr(v, key)
            if not (value is None and is_optional_type(anno)):
                try:
                    result[key] = to_json(value, anno)
                except JsonPatErrorWithPath as e:
                    raise _prepend_err_path(e, key)

        return result

    raise JsonPatClassError([], value_cls)

